var jid = d3.select('#job-id').text();
var dir_url = '/TMP/' + jid;

// CSS 补充
d3.selectAll('.sized-slct')
    .style('vertical-align', 'text-top')
    .style('min-width', '50px');;
d3.select('#img')
    .style('min-height', '1000px');
d3.select('#img-svg')
    .style('min-width', '350px');

// 选项卡
d3.selectAll('.tabbar a').on('click', function (event) {
    event.preventDefault();
    var a1 = d3.select(this);
    var did = a1.attr('href').replace('#', '');
    switch (did) {
        case 'bls':
            view_bls();
            break;
        case 'locate':
            locate_pps();
            break;
        case 'img':
            plot_img();
            break;
        default:
            d3.select('#err-msg').text('');
    }
    tabs_show(did);
});

// 启动即执行
tabs_show('bls');
view_bls();

// 监视控制面板变化
d3.selectAll('.sized-slct').on('change', board_change);
d3.selectAll('#control input').on('change', board_change);

// 点击选择表格内容
d3.selectAll('a.select-content')
    .attr('title', '单击对表格内容进行全选。')
    .on('click', function (event) {
        event.preventDefault();
        var a1 = d3.select(this);
        var tid = a1.attr('href').replace('#', '');
        select_text(tid);
    })
    ;

// save svg
d3.select('#save-svg').on('click', function () {
    saveSvgByPID('img-svg');
});

// Control board 发生变化
function board_change() {
    if (!d3.select('#bls').classed('hidden')) return view_bls();
    if (!d3.select('#locate').classed('hidden')) return locate_pps();
    if (!d3.select('#img').classed('hidden')) return plot_img();
}

function get_selected_opts(id) {
    var opts = [];
    d3.selectAll('#' + id + ' option').select(function () {
        if (this.selected) opts.push(this.value);
    });
    return opts;
}

function err_pids() {
    err_warn('请至少选择一个引物编号！');
}

function err_cname() {
    err_warn('请选择一条染色体！');
}

function err_none_acceptable() {
    err_warn('未发现可接受的定位结果！');
}

function err_font_color() {
    err_warn('若上下游引物定位间距超过最大可接受值，字体颜色将为 <b>darkgray</b>。', true);
}

function min_dist(dF, dR) {
    var min = {};
    dF.forEach(hf => {
        dR.forEach(hr => {
            if (hf.sstrand == hr.sstrand) return;
            var dist = 0;
            // 不可交错
            if (hf.sstart > hr.sstart) {
                if (hr.send >= hf.sstart) return;
                dist = hf.sstart - hr.send;
            } else if (hf.sstart < hr.sstart) {
                if (hf.send >= hr.sstart) return;
                dist = hr.sstart - hf.send;
            } else { // 出现相等了
                return;
            }
            if (Object.keys(min).length == 0 || dist < min.val) {
                min = { F: hf, R: hr, val: dist };
            }
        });
    });
    return min;
}

function locate_all_acceptable(dF, dR, max_dist) {
    var all = [];
    dF.forEach(hf => {
        dR.forEach(hr => {
            if (hf.sstrand == hr.sstrand) return;
            var dist = 0;
            // 不可交错
            if (hf.sstart > hr.sstart) {
                if (hr.send >= hf.sstart) return;
                dist = hf.sstart - hr.send;
            } else if (hf.sstart < hr.sstart) {
                if (hf.send >= hr.sstart) return;
                dist = hr.sstart - hf.send;
            } else { // 出现相等了
                return;
            }
            if (dist <= max_dist) all.push({ F: hf, R: hr, val: dist });
        });
    });
    return all;
}

function locate_pps() {
    var pids = get_selected_opts('pid-list');
    if (pids.length == 0) return err_pids();

    var cname = d3.select('#chr-list').property('value');
    if (cname == '') return err_cname();

    d3.select('#err-msg').text('');
    var pident = +d3.select('#pident').property('value');
    var qcovhsp = +d3.select('#qcovhsp').property('value');
    var max_dist = +d3.select('#max-distance').property('value');

    d3.tsv(dir_url + '/' + cname + '.tsv', (d) => {
        return {
            id: d.qseqid.replace(/_[FR]$/, ''),
            chr: d.sseqid,
            fr: d.qseqid.match(/_F$/) ? 'F' : 'R',
            pident: +d.pident,
            qcovhsp: +d.qcovhsp,
            sstrand: d.sstrand,
            // 主要是为方便 min_dist() 和 locate_all_acceptable
            sstart: (d.sstrand == 'minus') ? +d.send : +d.sstart,
            send: (d.sstrand == 'minus') ? +d.sstart : +d.send
        };
    }).then(function (data) {
        data = data.filter(d => {
            if (d.pident < pident) return false;
            if (d.qcovhsp < qcovhsp) return false;
            return d3.some(new Set(pids), x => x == d.id);
        });
        var tableS = d3.select('#tb-success'),
            tableF = d3.select('#tb-fail');
        tableS.html(''), tableF.html(''); // 清空

        if (data.length == 0) return err_warn('没有满足条件的数据！');

        // 必须 F, R 均有，否则就是没有好的定位
        // 直接分出两块，一块显示好的，一块显示没好定位的。
        var success = [], fail = [];
        pids.forEach(pid => {
            var dF = data.filter(d => d.id == pid && d.fr == 'F'),
                dR = data.filter(d => d.id == pid && d.fr == 'R');
            if (dF.length == 0 || dR.length == 0 || !fr_strand_ok(dF, dR)) return fail.push(pid);
            // 还要注意正反链
            // plus | minus
            // plus, minus | minus

            if (d3.select('#show-all').property('checked')) {
                Array.prototype.push.apply(success, locate_all_acceptable(dF, dR, max_dist));
            } else {
                success.push(min_dist(dF, dR));
            }
        });

        success.forEach((ds, idx) => {
            var d4tdId = {
                id: ds.F.id, chr: ds.F.chr, f: [ds.F.sstart, ds.F.send], r: [ds.R.sstart, ds.R.send]
            };
            var trF = tableS.append('tr').classed('alt-0', idx % 2 ? false : true),
                trR = tableS.append('tr').classed('alt-0', idx % 2 ? false : true);
            var tdId = trF.append('td').attr('rowspan', 2);
            trF.append('td').attr('rowspan', 2).text(ds.F.chr);
            trF.append('td').attr('rowspan', 2).text(ds.val);
            // 还是按 blast 的原规则展示 sstart, send 吧
            // 新建一个变量，避免一个 HSP 在多个 ds 中出现的情况, 2024.07.18
            var dsSE = {};
            // 2024.07.18
            ['F', 'R'].forEach(fr => {
                var s1 = ds[fr].sstart, e1 = ds[fr].send;
                if (ds[fr].sstrand == 'minus') {
                    // ds[fr].sstart = e1, ds[fr].send = s1;
                    dsSE[fr] = {'sstart': e1, 'send': s1};
                } else {
                    dsSE[fr] = {'sstart': s1, 'send': e1};
                }
            });
            // 2024.07.18
            ['fr', 'pident', 'qcovhsp', 'sstrand'].forEach(e => {
                trF.append('td').text(ds.F[e]);
                trR.append('td').text(ds.R[e]);
            });
            // 2024.07.18
            ['sstart', 'send'].forEach(e => {
                trF.append('td').text(dsSE.F[e]);
                trR.append('td').text(dsSE.R[e]);
            });
            if (ds.val > max_dist) {
                trF.style('color', 'darkgray');
                trR.style('color', 'darkgray');
                tdId.text(ds.F.id);
            } else {
                tdId.append('a').datum(d4tdId)
                    .attr('href', '#')
                    .attr('title', '点击获取定位区段序列，由于后台使用的是 blastdbcmd，速度较慢。')
                    .text(ds.F.id).on('click', function (event, d) {
                        event.preventDefault();
                        dispaly_seq(d);
                        d3.select('#dS-fa').classed('hidden', false);
                    })
                    ;
            }
        });
        tableF.selectAll('tr').data(fail).enter().append('tr').append('td')
            .text(d => d);
        d3.select('#l-success').classed('hidden', success.length > 0 ? false : true);
        d3.select('#l-fail').classed('hidden', fail.length > 0 ? false : true);
        if (success.length == 0) {
            err_none_acceptable();
        } else {
            err_font_color();
        }
    });
}

function view_bls() {
    var pids = get_selected_opts('pid-list');
    if (pids.length == 0) return err_pids();

    var cname = d3.select('#chr-list').property('value');
    if (cname == '') return err_cname();

    d3.select('#err-msg').text('');
    var pident = +d3.select('#pident').property('value');
    var qcovhsp = +d3.select('#qcovhsp').property('value');
    d3.tsv(dir_url + '/' + cname + '.tsv', (d) => {
        d.pident = +d.pident;
        d.qcovhsp = +d.qcovhsp;
        return d;
    }).then(function (data) {
        data = data.filter(d => {
            if (d.pident < pident) return false;
            if (d.qcovhsp < qcovhsp) return false;
            pid = d.qseqid.replace(/_[FR]$/, '');
            return d3.some(new Set(pids), x => x == pid);
        });
        if (data.length > 0) {
            var ks = Object.keys(data[0]); // keys
            var headerLine = '#' + ks.join('\t') + '\n';
            dF = data.filter(d => d.qseqid.match(/_F$/)).map(d => Object.values(d).join('\t'));
            dR = data.filter(d => d.qseqid.match(/_R$/)).map(d => Object.values(d).join('\t'));
            d3.select('#bls-of-F').text(headerLine + dF.join('\n'));
            d3.select('#bls-of-R').text(headerLine + dR.join('\n'));
        } else {
            d3.select('#bls-of-F').text('# No hits found!');
            d3.select('#bls-of-R').text('# No hits found!');
        }
    });
}

function plot_img() {
    // 清空
    d3.select('#img-svg').html('');

    var pids = get_selected_opts('pid-list');
    if (pids.length == 0) return err_pids();

    var cname = d3.select('#chr-list').property('value');
    if (cname == '') return err_cname();

    d3.select('#err-msg').text('');
    var pident = +d3.select('#pident').property('value');
    var qcovhsp = +d3.select('#qcovhsp').property('value');
    // 是否要设定 min-dist？  - 2021/07/12
    var max_dist = +d3.select('#max-distance').property('value');

    d3.tsv(dir_url + '/' + cname + '.tsv', (d) => {
        return {
            id: d.qseqid.replace(/_[FR]$/, ''),
            chr: d.sseqid,
            fr: d.qseqid.match(/_F$/) ? 'F' : 'R',
            pident: +d.pident,
            qcovhsp: +d.qcovhsp,
            sstrand: d.sstrand,
            // 主要是为方便 min_dist() 和 locate_all_acceptable
            sstart: (d.sstrand == 'minus') ? +d.send : +d.sstart,
            send: (d.sstrand == 'minus') ? +d.sstart : +d.send
        };
    }).then(function (data) {
        data = data.filter(d => {
            if (d.pident < pident) return false;
            if (d.qcovhsp < qcovhsp) return false;
            return d3.some(new Set(pids), x => x == d.id);
        });
        var success = [];
        pids.forEach(pid => {
            var dF = data.filter(d => d.id == pid && d.fr == 'F'),
                dR = data.filter(d => d.id == pid && d.fr == 'R');
            if (dF.length == 0 || dR.length == 0 || !fr_strand_ok(dF, dR)) return;
            if (d3.select('#show-all').property('checked')) {
                Array.prototype.push.apply(success, locate_all_acceptable(dF, dR, max_dist));
            } else {
                success.push(min_dist(dF, dR));
            }
        });
        if (success.length == 0) {
            d3.select('#img-support').classed('hidden', true);
            return err_none_acceptable();
        }
        d3.select('#img-support').classed('hidden', false);
        err_font_color();

        // 开始画图
        // 获取染色体长度信息
        d3.json(dir_url + '/done.json').then(function (done) {
            var chrLen = +done.chr_lens[cname];
            var fsize = {
                label: 10, chrName: 16, title: 30
            };
            var svg_w = 300, svg_h = 800,
                margin = { top: 50, bottom: 50, left: 60, right: 50 },
                // width = svg_w - margin.left - margin.right,
                height = svg_h - margin.top - margin.bottom;
            var ws = { chr: 10, branch: 20, gap: 5, sid: 60, pos: 80, link: 20 };
            var svg = d3.select('#img-svg').append('svg')
                .attr('width', svg_w).attr('height', svg_h)
                .attr('xmlns', 'http://www.w3.org/2000/svg')
                .attr('version', '1.1'),
                g = svg.append('g').attr('class', 'container')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
            var yScale = d3.scaleLinear().range([0, height]).domain([0, chrLen]);

            var d4plot = success.map(d => {
                return {
                    id: d.F.id,
                    sstart: d.F.sstart,
                    y_raw: yScale(d.F.sstart),
                    color: (d.val > max_dist) ? 'darkgray' : 'black',
                    dist: d.val
                };
            }).sort(function (a, b) {
                return a.sstart - b.sstart;
            });
            d4plot = arrange_y(d4plot, +fsize.label + 5);

            var gChr = g.append('g').attr('class', 'chr');
            // 主体
            gChr.append('rect').attr('class', 'main-chr')
                .attr('x', ws.pos + ws.gap + ws.branch)
                .attr('y', 0)
                .attr('width', ws.chr)
                .attr('height', height)
                .attr('stroke', '#000').attr('stroke-width', 1)
                .attr('fill', '#fff')
                ;
            // chr name
            gChr.append('text').attr('class', 'chr-name')
                .attr('x', ws.pos + ws.gap + ws.branch + ws.chr / 2).attr('y', -20)
                .attr('font-size', fsize.chrName + 'px')
                .attr('text-anchor', 'middle')
                .attr('fill', '#000')
                .text(cname);

            // 枝杈
            var gMarker = gChr.selectAll('g.marker').data(d4plot)
                .enter().append('g').attr('class', 'marker');
            gMarker.append('line')
                .attr('x1', ws.pos + ws.gap)
                .attr('y1', function (d) { return d.y; })
                .attr('x2', ws.pos + ws.gap + ws.branch)
                .attr('y2', function (d) { return d.y_raw; })
                .attr('stroke', '#000').attr('stroke-width', 1)
                ;
            gMarker.append('line')
                .attr('x1', ws.pos + ws.gap + ws.branch + ws.chr)
                .attr('y1', function (d) { return d.y_raw; })
                .attr('x2', ws.pos + ws.gap + ws.branch + ws.chr + ws.branch)
                .attr('y2', function (d) { return d.y; })
                .attr('stroke', '#000').attr('stroke-width', 1)
                ;
            // pos
            var unit = (chrLen / 1e6 >= 1) ? 1e6 : ((chrLen / 1e3 >= 1) ? 1e3 : 1);
            gMarker.append('text')
                .attr('x', ws.pos)
                .attr('y', function (d) { return d.y; })
                .attr('text-anchor', 'end')
                .attr('fill', function (d) { return d.color; })
                .text(function (d) {
                    return d3.format('.2f')(d.sstart / unit);
                });
            // id
            gMarker.append('text')
                .attr('x', ws.pos + (ws.gap + ws.branch) * 2 + ws.chr)
                .attr('y', function (d) { return d.y; })
                // .attr('text-anchor', 'end')
                .attr('fill', function (d) { return d.color; })
                .text(function (d) {
                    return d.id;
                });

            gMarker.selectAll('text')
                .attr('dy', 0.32 * fsize.label)
                .attr('font-size', fsize.label + 'px')
                ;

            gMarker.on('mouseover', function (event, d) {
                var x = event.clientX, y = event.clientY;
                d3.select('#tooltip')
                    .style('display', 'block')
                    .style('position', 'fixed')
                    .style('top', (y - 25) + 'px')
                    .style('left', (x + 20) + 'px')
                    .html('上游引物定位起始位置：' +
                        d3.format(',')(d.sstart) + ' bp<br>' +
                        '上下游引物定位间距：' +
                        d3.format(',')(d.dist) + ' bp');
                d3.select(this).classed('m-over', true);
            }).on('mouseout', function () {
                d3.select('#tooltip').style('display', 'none');
                d3.select(this).classed('m-over', false);
            });

            // 添加一个 y 坐标
            var gY = g.append('g').attr('class', 'y-axis')
                .attr("transform", "translate(" + (-ws.gap) + ",0)");
            gY.call(d3.axisLeft(yScale).ticks(null, 's'));
            gY.selectAll('text')
                .attr('font-size', fsize.label)
                .attr('dy', 0.32 * fsize.label);

            d3.select('#tb-chr').html('');
            d3.select('#tb-phy').html('');
            d3.select('#tb-chr').append('tr').selectAll('td').data([cname, chrLen]).enter()
                .append('td').text(d => d);
            d3.select('#tb-phy').selectAll('tr').data(d4plot).enter().append('tr')
                .classed("alt-0", (d, idx) => idx % 2 ? false : true)
                .selectAll('td').data(dTr => [dTr.id, cname, dTr.sstart, dTr.color, dTr.dist])
                .enter().append('td').text(d => d);
        });
    });
}

d3.select('#close-dS-fa').on('click', function () {
    d3.select('#dS-fa').classed('hidden', true);
});

// 错误或警告信息
function err_warn(txt, is_warn = false) {
    return d3.select('#err-msg').html(txt)
        .classed('emphasis', is_warn ? false : true);
}

// plus | minus
// plus, minus | minus
// 首先，认为 dF 和 dR 都不为空
function fr_strand_ok(dF, dR) {
    var sF = new Set(dF.map(d => d.sstrand)),
        sR = new Set(dR.map(d => d.sstrand));
    if (sF.size > 1 || sR.size > 1) return true;
    if (d3.union(sF, sR).size > 1) return true;
    return false;
}

function dispaly_seq(d) {
    var species = d3.select('#species').text();
    var middle = [], orders = [];
    if (d.f[0] < d.r[0]) {
        middle = [d.f[1] + 1, d.r[0] - 1];
        orders = ['f', 'm', 'r'];
    } else {
        middle = [d.r[1] + 1, d.f[0] - 1];
        orders = ['r', 'm', 'f'];
    }
    var preS = d3.select('#dS-fa pre');
    preS.html('>' + d.id + '-' + d.chr + '-' + d[orders[0]][0] + '\n'); // 更新
    d3.json('/pp_locator/blastdbcmd?species=' + species + '&cname=' + d.chr + '&batch=f,' + d.f.join(',') + ';r,' + d.r.join(',') + ';m,' + middle.join(',')).then(function (data) {
        orders.forEach(e => {
            preS.append('span')
                .classed(e + '-dna', true)
                .html(data[e]);
        });
    });
}